<script setup lang="tsx">
import { computed, ref, watch } from 'vue';
import type { SelectOption } from 'naive-ui';
import type { RouteMeta } from 'vue-router';
import { useBoolean } from '@sa/hooks';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { enableStatusOptions, menuTypeOptions } from '@/constants/business';
import { fetchAddMenu, fetchEditMenu, fetchGetAllPages } from '@/service/api';
import SvgIcon from '@/components/custom/svg-icon.vue';
import { getLocalIcons } from '@/utils/icon';
import {
  getLayoutAndPage,
  getPathParamFromRoutePath,
  getRoutePathByRouteName,
  getRoutePathWithParam,
  transformLayoutAndPageToComponent
} from './shared';

defineOptions({
  name: 'MenuOperateModal'
});

export type OperateType = NaiveUI.TableOperateType | 'addChild';

interface Props {
  /** 操作类型 */
  operateType: OperateType;
  /** the edit menu data or the parent menu data when adding a child menu */
  rowData?: ManageApi.MenuManage.Menu | null;
}

const props = defineProps<Props>();

interface Emits {
  (e: 'submitted'): void;
}

const emit = defineEmits<Emits>();

const visible = defineModel<boolean>('visible', {
  default: false
});
const { bool: loading, setBool: setLoading } = useBoolean();

const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();

const title = computed(() => {
  const titles: Record<OperateType, string> = {
    add: '新增菜单',
    addChild: '新增子菜单',
    edit: '编辑菜单'
  };
  return titles[props.operateType];
});

type Model = Pick<
  ManageApi.MenuManage.Menu,
  | 'menuType'
  | 'menuName'
  | 'routeName'
  | 'path'
  | 'component'
  | 'order'
  | 'status'
  | 'parentId'
  | 'keepAlive'
  | 'constant'
  | 'href'
  | 'hideInMenu'
  | 'activeMenu'
  | 'multiTab'
  | 'fixedIndexInTab'
> & {
  icon: string | null;
  layout: string;
  page: string | null;
  pathParam: string;
};

const model = ref(createDefaultModel());

function createDefaultModel(): Model {
  return {
    menuType: '1',
    menuName: '',
    routeName: '',
    path: '',
    pathParam: '',
    component: '',
    layout: '',
    page: null,
    icon: null,
    parentId: '0',
    status: false,
    keepAlive: false,
    constant: false,
    order: 0,
    href: null,
    hideInMenu: false,
    activeMenu: null,
    multiTab: false,
    fixedIndexInTab: null
  };
}

type RuleKey = Extract<keyof Model, 'menuName' | 'routeName' | 'path' | 'page'>;

const rules: Record<RuleKey, CommonType.WithArray<App.Global.FormRule>> = {
  menuName: defaultRequiredRule,
  routeName: defaultRequiredRule,
  path: defaultRequiredRule,
  page: defaultRequiredRule
};

const disabledMenuType = computed(() => props.operateType === 'edit');

const localIcons = getLocalIcons();
const localIconOptions = localIcons
  .filter(item => item.startsWith('menu-'))
  .map<SelectOption>(item => ({
    label: () => (
      <div class="flex-y-center gap-16px">
        <SvgIcon localIcon={item} class="text-icon" />
        <span>{item}</span>
      </div>
    ),
    value: item
  }));

const showLayout = computed(() => model.value.parentId === '0');
const showPage = computed(() => model.value.menuType === '1');

const layoutOptions: CommonType.Option[] = [
  {
    label: 'base 框架',
    value: 'base'
  },
  {
    label: 'blank 单页',
    value: 'blank'
  }
];

const expandedNames = ref(['base', 'meta']);

/** 页面组件选项 */
const allPages = ref<string[]>([]);

async function getAllPages() {
  const { data: pages } = await fetchGetAllPages();
  allPages.value = pages || [];
}

const pageOptions = computed(() => {
  const pages = [...allPages.value];

  if (model.value.routeName && !pages.includes(model.value.routeName)) {
    const parentRouteName = props.operateType === 'addChild' ? `${props.rowData?.routeName}_` : '';
    pages.unshift(parentRouteName + model.value.routeName);
  }

  const opts: CommonType.Option[] = pages.map(page => ({
    label: page,
    value: page
  }));

  return opts;
});

function handleInitModel() {
  model.value = createDefaultModel();

  if (!props.rowData) return;

  if (props.operateType === 'add') {
    Object.assign(model.value, { layout: 'base' });
  }

  if (props.operateType === 'addChild') {
    const { id, path } = props.rowData;

    Object.assign(model.value, { path: `${path}/`, parentId: id });
  }

  if (props.operateType === 'edit') {
    const { component, ...rest } = props.rowData;

    const { layout, page } = getLayoutAndPage(component);
    const { path, param } = getPathParamFromRoutePath(rest.path);

    Object.assign(model.value, rest, { layout, page, path, pathParam: param, icon: rest.meta.localIcon }, rest.meta);
  }
}

function closeDrawer() {
  visible.value = false;
}

function handleUpdateRoutePathByRouteName() {
  if (props.operateType === 'addChild') {
    model.value.path = getRoutePathByRouteName(`${props.rowData?.routeName}_${model.value.routeName}`);
    return;
  }
  if (model.value.routeName) {
    model.value.path = getRoutePathByRouteName(model.value.routeName);
  } else {
    model.value.path = '';
  }
}

/** 提交 */
async function handleSubmit() {
  await validate();
  const params = getSubmitParams();
  const { operateType } = props;
  const serviceMap = {
    add: fetchAddMenu,
    addChild: fetchAddMenu,
    edit: fetchEditMenu
  };
  setLoading(true);
  const { response, error } = await serviceMap[operateType](params);
  setLoading(false);
  if (error) return;
  window.$message?.success(response.data.msg);
  closeDrawer();
  emit('submitted');
}

function getSubmitParams(): ManageApi.MenuManage.AddMenuParams {
  const {
    layout,
    page,
    pathParam,
    icon: localIcon,
    keepAlive,
    constant,
    order,
    href,
    hideInMenu,
    activeMenu,
    multiTab,
    fixedIndexInTab,
    ...params
  } = model.value;

  const component = transformLayoutAndPageToComponent(layout, page!);
  const path = getRoutePathWithParam(model.value.path, pathParam);

  params.component = component;
  params.path = path;
  if (props.operateType === 'addChild') {
    params.routeName = `${props?.rowData?.routeName}_${model.value.routeName}`;
  }

  const meta: RouteMeta = {
    title: params.menuName,
    localIcon,
    keepAlive,
    constant,
    order,
    href,
    hideInMenu,
    activeMenu,
    multiTab,
    fixedIndexInTab
  };
  const menu = Object.assign(params, {
    meta: JSON.stringify(meta)
  });
  return menu;
}

watch(visible, () => {
  if (visible.value) {
    handleInitModel();
    restoreValidation();
    getAllPages();
  }
});

watch(
  () => model.value.routeName,
  () => {
    handleUpdateRoutePathByRouteName();
  }
);
</script>

<template>
  <NModal v-model:show="visible" :title="title" :mask-closable="false" preset="card" auto-focus class="w-800px">
    <NScrollbar class="h-480px pr-20px">
      <NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="100">
        <NCollapse v-model:expanded-names="expandedNames">
          <NCollapseItem title="基础配置" name="base">
            <NGrid cols="2">
              <NFormItemGi label="菜单类型" path="menuType">
                <NRadioGroup v-model:value="model.menuType" :disabled="disabledMenuType">
                  <NRadio v-for="item in menuTypeOptions" :key="item.value" :value="item.value" :label="item.label" />
                </NRadioGroup>
              </NFormItemGi>
              <NFormItemGi label="菜单名称" path="menuName">
                <NInput v-model:value="model.menuName" placeholder="请输入菜单名称" />
              </NFormItemGi>
              <NFormItemGi label="路由名称" path="routeName">
                <NInputGroup v-if="props.operateType === 'addChild'">
                  <NInputGroupLabel>{{ `${props?.rowData?.routeName}_` }}</NInputGroupLabel>
                  <NInput v-model:value="model.routeName" placeholder="请输入路由名称" />
                </NInputGroup>
                <NInput v-else v-model:value="model.routeName" placeholder="请输入路由名称" />
              </NFormItemGi>
              <NFormItemGi label="路由路径" path="path">
                <NInput v-model:value="model.path" disabled placeholder="请输入路由路径" />
              </NFormItemGi>
              <NFormItemGi label="路径参数" path="pathParam">
                <NInput v-model:value="model.pathParam" placeholder="请输入路径参数" />
              </NFormItemGi>
              <NFormItemGi v-if="showPage" label="页面组件" path="page">
                <NSelect v-model:value="model.page" :options="pageOptions" placeholder="请选择页面组件" />
              </NFormItemGi>
              <NFormItemGi label="菜单状态" path="status">
                <NRadioGroup v-model:value="model.status">
                  <NRadio
                    v-for="item in enableStatusOptions"
                    :key="item.value"
                    :value="item.value"
                    :label="item.label"
                  />
                </NRadioGroup>
              </NFormItemGi>
              <NFormItemGi label="隐藏菜单" path="hideInMenu">
                <NRadioGroup v-model:value="model.hideInMenu">
                  <NRadio :value="true" label="是" />
                  <NRadio :value="false" label="否" />
                </NRadioGroup>
              </NFormItemGi>
            </NGrid>
          </NCollapseItem>
          <NCollapseItem title="元数据" name="meta">
            <NGrid :cols="2">
              <NFormItemGi v-if="showLayout" label="布局" path="layout">
                <NSelect v-model:value="model.layout" :options="layoutOptions" placeholder="请选择布局" />
              </NFormItemGi>
              <NFormItemGi label="排序" path="order">
                <NInputNumber v-model:value="model.order" class="w-full" placeholder="请输入排序" />
              </NFormItemGi>
              <NFormItemGi v-if="model.hideInMenu" label="高亮的菜单" path="activeMenu">
                <NSelect
                  v-model:value="model.activeMenu"
                  :options="pageOptions"
                  clearable
                  placeholder="请选择高亮的菜单的路由名称"
                />
              </NFormItemGi>
              <NFormItemGi span="24" label="图标" path="icon">
                <NSelect
                  v-model:value="model.icon"
                  placeholder="请选择本地图标"
                  :options="localIconOptions"
                  clearable
                />
              </NFormItemGi>
              <NFormItemGi span="24" label="外链" path="href">
                <NInput v-model:value="model.href" placeholder="请输入外链" />
              </NFormItemGi>
              <template v-if="false">
                <NFormItemGi label="缓存路由" path="keepAlive">
                  <NRadioGroup v-model:value="model.keepAlive">
                    <NRadio :value="true" label="是" />
                    <NRadio :value="false" label="否" />
                  </NRadioGroup>
                </NFormItemGi>
                <NFormItemGi label="常量路由" path="constant">
                  <NRadioGroup v-model:value="model.constant">
                    <NRadio :value="true" label="是" />
                    <NRadio :value="false" label="否" />
                  </NRadioGroup>
                </NFormItemGi>
                <NFormItemGi label="支持多标签页" path="multiTab">
                  <NRadioGroup v-model:value="model.multiTab">
                    <NRadio :value="true" label="是" />
                    <NRadio :value="false" label="否" />
                  </NRadioGroup>
                </NFormItemGi>
                <NFormItemGi label="固定在页签中的序号" path="fixedIndexInTab">
                  <NInputNumber
                    v-model:value="model.fixedIndexInTab"
                    class="w-full"
                    clearable
                    placeholder="请输入固定在页签中的序号"
                  />
                </NFormItemGi>
              </template>
            </NGrid>
          </NCollapseItem>
        </NCollapse>
      </NForm>
    </NScrollbar>
    <template #footer>
      <NSpace justify="end" :size="16">
        <NButton @click="closeDrawer">取消</NButton>
        <NButton type="primary" :loading="loading" @click="handleSubmit">确认</NButton>
      </NSpace>
    </template>
  </NModal>
</template>

<style scoped></style>
